package lysjmaster.base.auth.service;

import com.auth0.jwt.interfaces.DecodedJWT;
import lysjmaster.base.auth.domain.Permission;
import lysjmaster.base.auth.domain.RolePermission;
import lysjmaster.base.auth.domain.User;
import lysjmaster.base.auth.repository.PermissionRepository;
import lysjmaster.base.auth.repository.RolePermissionRepository;
import lysjmaster.base.auth.repository.UserRepository;
import lysjmaster.base.auth.vo.PermissionCacheVO;
import lysjmaster.base.common.CommonConstant;
import lysjmaster.base.common.authentication.AuthenticationInterface;
import lysjmaster.base.common.authentication.AuthenticationResultVO;
import lysjmaster.base.common.cache.LocalCacheUtil;
import lysjmaster.base.common.jwt.JwtUtil;
import lysjmaster.base.common.web.ParamUtil;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author Created by zk on 2019-12-06 16:27
 * @description
 */
@Service
public class AdminAuthentication implements AuthenticationInterface {
    @Resource
    private PermissionRepository permissionRepository;
    @Resource
    private UserRepository userRepository;
    @Resource
    private LocalCacheUtil localCacheUtil;
    @Resource
    private RolePermissionRepository rolePermissionRepository;

    private Map<PermissionCacheVO, Set<String>> adminPermissionRoleCache;

    @PostConstruct
    public void init(){
        adminPermissionRoleCache = initCache();
    }


    @Override
    public String getIss() {
        return CommonConstant.ADMIN_ISSUER;
    }

    @Override
    public AuthenticationResultVO authentication(String token, Map<String, Object> payloadMap, String path) {
        String userId = (String) payloadMap.get(CommonConstant.JWT_SUB);
        if (ParamUtil.isBlank(userId)) {
            return new AuthenticationResultVO(false, "token信息异常,请重新登录");
        }
        User user = userRepository.findOne(userId);
        if (user == null || CommonConstant.DEL_FLAG.equals(user.getDelFlag())) {
            return new AuthenticationResultVO(false, "查无该token信息,请重新登录");
        }
        String key = user.getUsername();
        DecodedJWT jwt = JwtUtil.verifier(token, key);
        //判断是否拥有该路径权限，目前策略为数据库若无记录的路径，则全部放行
        //获取该path支持的角色列表
        Set<String> roleIds = adminPermissionRoleCache.get(path);
        //若角色列表不为null，则需要进行权限判断
        if (roleIds != null) {
            //若在角色列表中匹配不到该角色，则为无权限
            if (!roleIds.contains(userId)) {
                return new AuthenticationResultVO(false, "您尚未拥有该权限");
            }
        }
        //判断用户token是否需要刷新
        Date expires = jwt.getExpiresAt();
        Date now = new Date();
        if ((now.getTime() - expires.getTime()) < CommonConstant.ADMIN_TOKEN_REFRESH * 60 * 1000) {
            token = JwtUtil.copyToken(payloadMap, CommonConstant.ADMIN_TOKEN_EXPIRATION, key);
        }
        return new AuthenticationResultVO(true, token);
    }

    private Map<PermissionCacheVO,Set<String>> initCache(){
        List<Permission> permissions = permissionRepository.findByLevel(3);
        //构造LRU缓存，大小为查找到的三级权限数
        Map<PermissionCacheVO,Set<String>> cache  = new ConcurrentHashMap<>(permissions.size());
        //获取所有三级菜单的id
        Set<String> permissionIds = permissions.parallelStream().map(Permission::getId).collect(Collectors.toSet());
        //获取这些三级菜单的角色关系
        List<RolePermission> rolePermissions = rolePermissionRepository.findByPermissionIdIn(permissionIds);
        //构造权限Id-角色列表
        Map<String, Set<String>> permissionIdRoleMap = rolePermissions.parallelStream()
                .collect(Collectors.groupingBy(RolePermission::getPermissionId,
                        Collectors.mapping(RolePermission::getRoleId, Collectors.toSet())));
        for (Permission permission : permissions) {
            PermissionCacheVO vo = new PermissionCacheVO(permission.getId(),permission.getPath());
            Set<String> roleIds = permissionIdRoleMap.get(vo.getId());
            if(roleIds == null){
                continue;
            }
            cache.put(vo,roleIds);
        }
        return cache;
    }
}
